package org.cll.lagou.study.minicat.server;

import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.regex.Matcher;

/**
 * 描述信息:
 * Minicat主类（启动类）
 * @author CLL
 * @version 1.0
 * @date 2020/7/2 8:24
 */
public class IBootstrap {

    private static final Logger logger = LoggerFactory.getLogger(IBootstrap.class);

    @Getter
    @Setter
    private int port = 8080;
    
    private final String HELLO_MINICAT = "Hello I am Minicat";

    /*@Getter
    @Setter
    private List<IHost> hostList;*/

    @Getter
    @Setter
    private Map<String, IHost> hostMap = new HashMap<>();

    private void initHost() throws Exception {
        // 加载server.xml并解析
        InputStream resourceAsStream = getResourceAsStream("minicat-server-2.xml");

        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(resourceAsStream);
        Element rootElement = document.getRootElement();

        List<Element> serviceList = rootElement.selectNodes("//minicat-service");

        for (Element service : serviceList) {
            Element connector = (Element) service.selectSingleNode("//minicat-connector");
            String port = connector.attributeValue("port");
            logger.info("======[initServlet] this service listener port is = {}", port);
            this.port = Integer.parseInt(port);

            Element engine = (Element) service.selectSingleNode("//miniat-engine");

            List<Element> hostList = engine.selectNodes("//minicat-host");
            for (Element host : hostList) {

                IHost iHost = new IHost();
                String hostname = host.attributeValue("name");
                String appBase = host.attributeValue("appBase");
                iHost.setName(hostname);
                if (null == iHost.getContextMap()) {
                    iHost.setContextMap(new HashMap<>());
                }
                doLoadContextByHost(appBase, iHost);

                hostMap.put(hostname, iHost);
            }

            System.out.println("==========================>>>>>>>>>>>>>>>>>初始化完成");

        }
    }

    /**
     * Minicat启动需要进行的一些列操作
     * 只能接收一次请求
     * minicat 6.0部署多WEB应用项目
     */
//    public void start() throws IOException, DocumentException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    public void start() throws Exception {

        // 加载wrapper minicat 6.0
//        loadDefaultServlet();

        // 加载多个项目 minicat 7.0
//        loadContext();

        // 加载默认host[localhost]下多个项目 minicat 8.0
//        initServlet();

        // 加载多个虚拟主机下多个项目 minicat 9.0
        initHost();

        for (Map.Entry<String, AbstractHttpServlet> map : servletMap.entrySet()) {
            logger.info("======>>> key = {}, value = {}", map.getKey(), map.getValue());
        }

        for (Map.Entry<String, IHost> map : hostMap.entrySet()) {
            String key = map.getKey();
            logger.info("======>>> key = {}, value = {}", key, map.getValue());
            boolean keyFlag = hostMap.containsKey(key);
            if (keyFlag) {
                logger.info(" value = {}", hostMap.get(key));
            }
        }

        // 定义连接器线程池
        ExecutorService singleThreadPool = new ThreadPoolExecutor(2, 4,
                10, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1024),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        logger.debug("====== 启动Minicat 5.0 on {}", port);
        // 监听端口
        ServerSocket serverSocket = new ServerSocket(port);

        while (true) {
            // 获取请求
            Socket socket = serverSocket.accept();

            /*
            minicat 1.0
            接收浏览器请求，http://localhost:8080/
            并返回固定字符串到浏览器:Hello, I am Minicat.
             */
            /*OutputStream outputStream = socket.getOutputStream();
            String responseContext = HttpProtocolUtil.getHttpResponse200(HELLO_MINICAT);

            outputStream.write(responseContext.getBytes());
            outputStream.close();*/

            /*
            minicat 2.0
             */
            /*InputStream inputStream = socket.getInputStream();
            IRequest iRequest = new IRequest(inputStream);

            IResponse iResponse = new IResponse(socket.getOutputStream());
            iResponse.outputHtml(iRequest.getUrl());*/

            /*
            minicat 3.0
             */
            /*InputStream inputStream = socket.getInputStream();
            IRequest iRequest = new IRequest(inputStream);
            IResponse iResponse = new IResponse(socket.getOutputStream());

            AbstractHttpServlet httpServlet = servletMap.get(iRequest.getUrl());
            if (null == httpServlet) {
                iResponse.outputHtml(iRequest.getUrl());
            } else {
                httpServlet.service(iRequest, iResponse);
            }
            socket.close();*/

            /*
            多线程改造 4.0
             */
            /*IRequestProcessor iRequestProcessor = new IRequestProcessor(socket, servletMap);
            new Thread(iRequestProcessor).start();*/

            /*
            线程池多线程改造 5.0
             */
//            IRequestProcessor iRequestProcessor = new IRequestProcessor(socket, servletMap);
            IRequestProcessor iRequestProcessor = new IRequestProcessor(socket, hostMap);
            System.out.println(iRequestProcessor);
            singleThreadPool.execute(iRequestProcessor);

        }
    }

    /**
     * 加载server.xml配置文件并解析
     */
    private void initServlet() throws Exception{
        loadDefaultServlet();

        // 加载server.xml并解析
        InputStream resourceAsStream = getResourceAsStream("minicat-server.xml");

        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(resourceAsStream);
        Element rootElement = document.getRootElement();

        List<Element> serviceList = rootElement.selectNodes("//minicat-service");

        for (Element service : serviceList) {
            Element connector = (Element) service.selectSingleNode("//minicat-connector");
            String port = connector.attributeValue("port");
            logger.info("======[initServlet] this service listener port is = {}", port);
            this.port = Integer.parseInt(port);

            Element engine = (Element) service.selectSingleNode("//miniat-engine");
            Element host = (Element) engine.selectSingleNode("//minicat-host");

//            String hostname = host.attributeValue("name");
            String appBase = host.attributeValue("appBase");

            doLoadContext(appBase);
        }
    }


    private Map<String, AbstractHttpServlet> servletMap = new HashMap<>();

    /**
     * 加载minicat默认配置的web.xml文件
     */
//    private void loadDefaultServlet() throws DocumentException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    private void loadDefaultServlet() throws Exception {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        doLoadServlet(resourceAsStream, null, null);
    }


    private void doLoadServlet(InputStream webFileInputStream, String contextPath, String mapper) throws Exception{
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(webFileInputStream);

        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("servlet");

        for (int i = 0; i < list.size(); i++) {
            Element servletElement = list.get(i);
            Element servletNameElement = (Element) servletElement.selectSingleNode("servlet-name");
            String servletName = servletNameElement.getStringValue();
            Element servletClassElement = (Element) servletElement.selectSingleNode("servlet-class");
            String servletClass = servletClassElement.getStringValue();

            // 根据servletName获取servlet-mapping
            Element servletMappingElement = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            String urlPattern = servletMappingElement.selectSingleNode("url-pattern").getStringValue();

            if (null == contextPath || "".equals(contextPath)) {
                servletMap.put(urlPattern, (AbstractHttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
            } else {
                String formatPath = formatReplaceAllClassName(contextPath, servletClass);
                logger.info("====== formatPath = {}", formatPath);

                ClassLoader myClassLoader = new IClassLoader();
                Class<?> demoClass = myClassLoader.loadClass(formatPath);
                if (null == demoClass) {
                    logger.error("****** 加载类失败， 类路径= {}", formatPath);
                } else {
                    logger.info("====== 加载类成功， 类路径= {}", formatPath);
                    // 需要组装成/web_demo2/demo/test
                    String demoUrlPath = "/" + mapper + urlPattern;
                    Object demoObj = demoClass.getDeclaredConstructor().newInstance();
                    logger.info("====== obj = {}", demoObj);
                    if (demoObj instanceof AbstractHttpServlet) {
                        AbstractHttpServlet demoServlet = (AbstractHttpServlet) demoObj;
                        servletMap.put(demoUrlPath, demoServlet);
                    }
                }
            }
        }
    }

    private void doLoadServletUserMapper(InputStream webFileInputStream, String contextPath, String mapper, IContext iContext) throws Exception{
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(webFileInputStream);

        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("servlet");

        for (int i = 0; i < list.size(); i++) {
            IMapper iMapper = new IMapper();
            Element servletElement = list.get(i);
            Element servletNameElement = (Element) servletElement.selectSingleNode("servlet-name");
            String servletName = servletNameElement.getStringValue();
            Element servletClassElement = (Element) servletElement.selectSingleNode("servlet-class");
            String servletClass = servletClassElement.getStringValue();

            // 根据servletName获取servlet-mapping
            Element servletMappingElement = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            String urlPattern = servletMappingElement.selectSingleNode("url-pattern").getStringValue();
            iMapper.setUrl(urlPattern);
            if (null == contextPath || "".equals(contextPath)) {
                servletMap.put(urlPattern, (AbstractHttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
            } else {
                String formatPath = formatReplaceAllClassName(contextPath, servletClass);
                logger.info("====== formatPath = {}", formatPath);

                ClassLoader myClassLoader = new IClassLoader();
                Class<?> demoClass = myClassLoader.loadClass(formatPath);
                if (null == demoClass) {
                    logger.error("****** 加载类失败， 类路径= {}", formatPath);
                } else {
                    logger.info("====== 加载类成功， 类路径= {}", formatPath);
                    // 需要组装成/web_demo2/demo/test
                    String demoUrlPath = "/" + mapper + urlPattern;
                    Object demoObj = demoClass.getDeclaredConstructor().newInstance();
                    logger.info("====== obj = {}", demoObj);
                    if (demoObj instanceof AbstractHttpServlet) {
                        AbstractHttpServlet demoServlet = (AbstractHttpServlet) demoObj;
                        iMapper.setServlet(demoServlet);
//                        servletMap.put(demoUrlPath, demoServlet);
                    }
                }
            }

            iContext.getMapperMap().put(urlPattern, iMapper);
        }
    }

    private void doLoadServletWith(InputStream webFileInputStream, String contextPath, String mapper) throws Exception{
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(webFileInputStream);

        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("servlet");

        for (int i = 0; i < list.size(); i++) {
            Element servletElement = list.get(i);
            Element servletNameElement = (Element) servletElement.selectSingleNode("servlet-name");
            String servletName = servletNameElement.getStringValue();
            Element servletClassElement = (Element) servletElement.selectSingleNode("servlet-class");
            String servletClass = servletClassElement.getStringValue();

            // 根据servletName获取servlet-mapping
            Element servletMappingElement = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            String urlPattern = servletMappingElement.selectSingleNode("url-pattern").getStringValue();

            if (null == contextPath || "".equals(contextPath)) {
                servletMap.put(urlPattern, (AbstractHttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
            } else {
                String formatPath = formatReplaceAllClassName(contextPath, servletClass);
                logger.info("====== formatPath = {}", formatPath);

                ClassLoader myClassLoader = new IClassLoader();
                Class<?> demoClass = myClassLoader.loadClass(formatPath);
                if (null == demoClass) {
                    logger.error("****** 加载类失败， 类路径= {}", formatPath);
                } else {
                    logger.info("====== 加载类成功， 类路径= {}", formatPath);
                    // 需要组装成/web_demo2/demo/test
                    String demoUrlPath = "/" + mapper + urlPattern;
                    Object demoObj = demoClass.getDeclaredConstructor().newInstance();
                    logger.info("====== obj = {}", demoObj);
                    if (demoObj instanceof AbstractHttpServlet) {
                        AbstractHttpServlet demoServlet = (AbstractHttpServlet) demoObj;
                        servletMap.put(demoUrlPath, demoServlet);
                    }
                }
            }
        }
    }

    /**
     * 将web.xml中配置的servlet中servlet-class转换成本地文件路径
     * @param contextPath   项目路径下WEB-INF目录路径
     * @param servletClassName  servlet-class中配置路径
     * @return  转换之后的路径
     */
    private String formatReplaceAllClassName(String contextPath, String servletClassName){
        String classPath = servletClassName.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
        String packageName = contextPath + File.separator + "classes";
        return packageName + File.separator + classPath + ".class";
    }


    /**
     * minicat-7.0配置多个context
     * @throws Exception
     */
    private void loadContext() throws Exception {
        loadDefaultServlet();

        // 加载server.xml并解析
        InputStream resourceAsStream = getResourceAsStream("server.xml");

        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(resourceAsStream);
        Element rootElement = document.getRootElement();

        List<Element> list = rootElement.selectNodes("//minicat-context");

        for (Element context : list) {
            String applicationBasePath = context.attributeValue("applicationBasePath");
            String path = context.attributeValue("path");
            logger.info("[loadContext]appPath = {}, path = {}", applicationBasePath, path);
            doLoadContextWrapper(applicationBasePath, path);
        }
    }

    private InputStream getResourceAsStream(String resourcePath){
        return this.getClass().getClassLoader().getResourceAsStream(resourcePath);
    }

    private void doLoadContext(String directoryPath) throws Exception {
        File file = new File(directoryPath);
        if (file.exists() && file.isDirectory()) {
            String[] childFileNameArr = file.list();
            if (null == childFileNameArr || 0 == childFileNameArr.length) {
                logger.error("****** host中配置目录下不存在文件");
            } else {
                for (String fileName : childFileNameArr) {
                    logger.info("[doLoadContext]====== child file name is = {}", fileName);
                    doLoadContextWrapper(directoryPath + File.separator + fileName, fileName);
                }
            }
        } else {
            logger.error("******  host中配置错误。directoryPath = {}", directoryPath);
        }
    }


    private void doLoadContextByHost(String directoryPath, IHost iHost) throws Exception {
        File file = new File(directoryPath);
        if (file.exists() && file.isDirectory()) {
            String[] childFileNameArr = file.list();
            if (null == childFileNameArr || 0 == childFileNameArr.length) {
                logger.error("****** host中配置目录下不存在文件");
            } else {
                for (String fileName : childFileNameArr) {
                    logger.info("[doLoadContext]====== child file name is = {}", fileName);

                    IContext iContext = new IContext();
                    iContext.setName(fileName);
                    if (null == iContext.getMapperMap()) {
                        iContext.setMapperMap(new HashMap<>(2));
                    }
                    doLoadMapperByContext(directoryPath + File.separator + fileName, fileName, iContext);
                    iHost.getContextMap().put(fileName, iContext);
                }
            }
        } else {
            logger.error("******  host中配置错误。directoryPath = {}", directoryPath);
        }
    }

    private void doLoadContextWrapper(String directoryPath, String contextPath) throws Exception {
        File context = new File(directoryPath);
        if (context.exists() && context.isDirectory()) {
            File webInfDirectory = new File(context, "WEB-INF");
            if (webInfDirectory.exists() && webInfDirectory.isDirectory()) {
                File web = new File(webInfDirectory, "web.xml");
                if (web.exists() && web.isFile()) {
                    doLoadServlet(new FileInputStream(web), webInfDirectory.getAbsolutePath(), contextPath);
                } else {
                    logger.error("====== context = {} does not have web.xml, this is not web application", directoryPath);
                }
            } else {
                logger.error("====== context = {} does not have WEB-INF directory, this is not web application", directoryPath);
            }
        } else {
            logger.error("====== context = {} is not exists", directoryPath);
        }
    }

    private void doLoadMapperByContext(String directoryPath, String contextPath, IContext iContext) throws Exception {
        File context = new File(directoryPath);
        if (context.exists() && context.isDirectory()) {
            File webInfDirectory = new File(context, "WEB-INF");
            if (webInfDirectory.exists() && webInfDirectory.isDirectory()) {
                File web = new File(webInfDirectory, "web.xml");
                if (web.exists() && web.isFile()) {
                    doLoadServletUserMapper(new FileInputStream(web), webInfDirectory.getAbsolutePath(), contextPath, iContext);
                } else {
                    logger.error("====== context = {} does not have web.xml, this is not web application", directoryPath);
                }
            } else {
                logger.error("====== context = {} does not have WEB-INF directory, this is not web application", directoryPath);
            }
        } else {
            logger.error("====== context = {} is not exists", directoryPath);
        }
    }



    /**
     * 启动方法
     * @param args  启动参数
     */
    public static void main(String[] args) {

        logger.info("====== 启动初始化工作");
        IBootstrap iBootstrap = new IBootstrap();
        try {
            iBootstrap.start();
        } catch (IOException | DocumentException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            logger.error("****** 启动minicat抛出异常" + e.getMessage(), e);
            e.printStackTrace();
        } catch (Exception e) {
            logger.error("****** 启动minicat抛出minicat 6.0异常" + e.getMessage(), e);
            e.printStackTrace();
        }
    }
}